/**
 * Collision-resistant UID generator for browsers and node.
 * Sequential for fast db lookups and recency sorting.
 * Safe for element IDs and server-side lookups.
 *
 * Extracted from CLCTR
 *
 * https://github.com/paralleldrive/cuid
 */
function pad(num: string, size: number) {
  const s = '000000000' + num
  return s.substring(s.length - size)
}

let getRandomValue: any

const crypto = typeof window !== 'undefined' &&
  // @ts-ignore
  (window.crypto || window.msCrypto) || typeof self !== 'undefined' && self.crypto

if (crypto) {
  const lim = Math.pow(2, 32) - 1
  getRandomValue = function () {
    return Math.abs(crypto.getRandomValues(new Uint32Array(1))[0] / lim)
  }
} else {
  getRandomValue = Math.random
}

const env = typeof window === 'object' ? window : self
const globalCount = Object.keys(env).length
const mimeTypesLength = navigator.mimeTypes ? navigator.mimeTypes.length : 0
const clientId = pad(
  (mimeTypesLength + navigator.userAgent.length).toString(36) +
  globalCount.toString(36), 4)

function fingerprint() {
  return clientId
}

let counter = 0
const blockSize = 4
const base = 36
const discreteValues = Math.pow(base, blockSize)

function randomBlock(): string {
  return pad((getRandomValue() * discreteValues << 0).toString(base), blockSize)
}

function safeCounter(): number {
  counter = counter < discreteValues ? counter : 0
  counter++ // this is not subliminal
  return counter - 1
}

export function cuid(): string {
  // Starting with a lowercase letter makes
  // it HTML element ID friendly.
  const letter = 'c' // hard-coded allows for sequential access

  // timestamp
  // warning: this exposes the exact date and time
  // that the uid was created.
  const timestamp = (new Date().getTime()).toString(base)

  // Prevent same-machine collisions.
  const counter = pad(safeCounter().toString(base), blockSize)

  // A few chars to generate distinct ids for different
  // clients (so different computers are far less
  // likely to generate the same id)
  const print = fingerprint()

  // Grab some more chars from Math.random()
  const random = randomBlock() + randomBlock()

  return letter + timestamp + counter + print + random
}

cuid.slug = function slug() {
  const date = new Date().getTime().toString(36)
  const counter = safeCounter().toString(36).slice(-4)
  const print = fingerprint().slice(0, 1) + fingerprint().slice(-1)
  const random = randomBlock().slice(-2)

  return date.slice(-2) + counter + print + random
}

cuid.isCuid = function isCuid(stringToCheck: string): boolean {
  if (typeof stringToCheck !== 'string') return false
  return stringToCheck.startsWith('c')
}

cuid.isSlug = function isSlug(stringToCheck: string): boolean {
  if (typeof stringToCheck !== 'string') return false
  const stringLength = stringToCheck.length
  return stringLength >= 7 && stringLength <= 10

}

cuid.fingerprint = fingerprint
